﻿///<FileHeader>
///     <FileName>ModelElementDescriptor.cs</FileName>
///     <Author>Kiran Chand Palakkattiri</Author>
///     <Project>
///         <Name>Property Visibility Extensions for DSL</Name>
///         <Url>http://kiranchand.codeplex.com</Url>
///     </Project>
///     <CreationDate>August 30, 2009.</CreationDate>
///     <Contents>
///         <Content type="class" name="AdvancedModelTypeDescriptor"
///             baseclass="ElementTypeDescriptor"
///             comments="Extended type descriptor for DSL model elements supports dynamic property visibility behavior."/>
///     </Contents>
///     <Dependencies>
///         <Dependency type="reference" namespace="System" />
///         <Dependency type="reference" namespace="System.Collections.Generic" />
///         <Dependency type="reference" namespace="System.ComponentModel" />
///         <Dependency type="reference" namespace="System.Linq" />
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1"/>
///         <Dependency type="reference" namespace="Microsoft.VisualStudio.Modeling.Design"
///             assembly="Microsoft.VisualStudio.Modeling.Sdk" version="9.0.0.0" prerequisites="Visual Studio SDK 1.1" />
///     </Dependencies>
///     <Modifications>
///         <Modification changeset="1004" date="September 07, 2009" author="Kiran Chand Palakkattiri">
///             <Target="function" targetname="CreateDescriptor(....)" type="update"
///                 comment="fixed the issues custom editor in read only mode;
///                 disabled creating new type descriptor for hidden properties;
///                 added code for supporting the role player visibility;
///                 refactored the code for easy arrangement"/>
///             <Target="class" targetname="classname" type="update"
///                 comment="corrected the spelling mistakes in the class name"/>
///             <Target="property" targetname="Property descritptor cache" type="delete"
///                 comment="null reference errors in maintaining cache."/>
///             <Target="property" targetname="Property visibility cache" type="delete"
///                 comment="null reference errors in maintaining cache."/>
///             <Target="property" targetname="DomainPropertyIDCollection" type="create"
///                 comment="created new property."/>
///             <Target="constructor" targetname="AdvancedModelTypeDescriptor" type="update"
///                 comment="Included visibility decorator check;
///                 added code for instantiating domain role players and domain role ids collections"/>
///             <Target="property" targetname="DomainRolePlayers" type="create"
///                 comment="Added for supporting domain role players visibility."/>
///             <Target="property" targetname="DomainRolePlayerIDCollection" type="create"
///                 comment="Added for supporting domain role players visibility."/>
///             <Target="function" targetname="GetUpdatedDomainRoleDescriptors" type="create"
///                 comment="Added for supporting domain role players visibility."/>
///             <Target="function" targetname="GetUpdatedDomainPropertyDescriptors" type="create"
///                 comment="refctored for easy understanding of code"/>
///             <Target="function" targetname="CreateRolePlayerPropertyDescriptor" type="create"
///                 comment="added for supporting role players"/>
///             <Target="function" targetname="GetCustomAttributes" type="create"
///                 comment="refactored the code for avoiding redundancy."/>
///         </Modification>
///         <Modification changeset="1005" date="September 08, 2009" author="Kiran Chand Palakkattiri">
///             <Target="constructor" targetname="AdvancedModelTypeDescriptor(....)" type="update"
///                 comment="fixed the issues in mixed implementations of property descriptors;"/>
///         </Modification>
///         <Modification changeset="1007" date="April 08, 2010" author="Kiran Chand Palakkattiri">
///             <Target="property" targetname="ModelElement" type="update"
///                 comment="brought in the support for getting and setting
///                 values from and to the baseclass's field using reflection "/>
///         </Modification>
///     </Modifications>
///     <Version>4.0.0.0</Version>
///</FileHeader>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Design;

namespace Kiranchand.Dsl.Extensions.PropertyVisibility
{
    /// <summary>
    /// Extended type descriptor for DSL model elements supports dynamic property visibility behavior.
    /// </summary>
    public sealed class ModelElementDescriptor : ElementTypeDescriptor
    {
        /// <summary>
        /// Holds the default descriptors for the type from the base class.
        /// </summary>
        private PropertyDescriptorCollection DefaultDescriptors { get; set; }

        /// <summary>
        /// Holds the domain properties of the current model standardValueProvider instance.
        /// </summary>
        private List<DomainPropertyInfo> DomainProperties { get; set; }

        /// <summary>
        /// The list of the domain property ids for verification from the 
        /// </summary>
        private List<Guid> DomainPropertyIDCollection { get; set; }

        /// <summary>
        /// Holds the domain roles of the current model standardValueProvider instance.
        /// </summary>
        private List<DomainRoleInfo> DomainRolePlayers { get; set; }

        /// <summary>
        /// Gets or sets the domain role player ids that are to be shown on property grid
        /// </summary>
        private List<Guid> DomainRolePlayerIDCollection { get; set; }

        /// <summary>
        /// Encapsulates the model element instance in the base class
        /// </summary>
        public new ModelElement ModelElement
        {
            get
            {
                return this.GetFieldValue("element") as ModelElement;
            }
            internal set
            {
                this.SetFieldValue("element", value);
            }
        }

        /// <summary>
        /// Locking! Not coded
        /// </summary>
        private static object syncRoot = new object();

        /// <summary>
        /// Instantiates the instance of  ModelTypeDescriptor and intialize the states of variables.
        /// </summary>
        /// <param name="selectedElement">instance of the selected model standardValueProvider</param>
        public ModelElementDescriptor(ModelElement selectedElement)
            : base(selectedElement)
        {

            // populate the data for domain properties

            // get the domain properties
            this.DomainProperties = this.ModelElement.GetDomainClass().AllDomainProperties.ToList();

            // extract domain property ids
            this.DomainPropertyIDCollection = new List<Guid>();

            for (int i = 0; i < this.DomainProperties.Count; i++)
            {
                DomainPropertyInfo propertyInfo = this.DomainProperties[i];
                if (propertyInfo != null
                    && !Guid.Empty.Equals(propertyInfo.Id))
                {
                    this.DomainPropertyIDCollection.Add(propertyInfo.Id);
                }

            }

            // populate the data for roleplayers

            // get the domain role players
            this.DomainRolePlayers = this.ModelElement.GetDomainClass().AllDomainRolesPlayed.ToList();
            // instantiate the domain role player guid collection
            this.DomainRolePlayerIDCollection = new List<Guid>();
            // include the opposite domain role ??
            bool bIncludeOppositeRole = this.IncludeOppositeRolePlayerProperties(this.ModelElement);
            // iterate through the domain roles
            foreach (DomainRoleInfo sourceRole in this.DomainRolePlayers)
            {
                // check multiplicity validations
                if (bIncludeOppositeRole
                    && (sourceRole.Multiplicity == Multiplicity.ZeroOne
                    || sourceRole.Multiplicity == Multiplicity.One))
                {
                    // populate & verify opposite domain role properties
                    DomainRoleInfo oppositeDomainRole = sourceRole.OppositeDomainRole;
                    if (oppositeDomainRole.RolePlayer.NameDomainProperty != null
                        && !this.DomainRolePlayerIDCollection.Contains(sourceRole.Id)
                        && this.ShouldCreateRolePlayerPropertyDescriptor(this.ModelElement, sourceRole))
                    {
                        // add the domain role id
                        this.DomainRolePlayerIDCollection.Add(sourceRole.Id);
                    }
                }

            }

        }
        /// <summary>
        /// Returns a collection of domain property descriptors that contains all the properties of the described type.
        /// </summary>
        /// <returns>The collection of domain property descriptors that contains all the properties of the described type.</returns>
        public override PropertyDescriptorCollection GetProperties()
        {
            lock (syncRoot)
            {
                return this.GetProperties(new Attribute[0] { });
            }
        }

        /// <summary>
        /// Returns a collection of domain property descriptors that contains all the
        /// properties of the described type, filtered by the specified attributes.
        /// </summary>
        /// <param name="attributes">The array of custom attributes on which to filter.</param>
        /// <returns>The collection of domain property descriptors that contains all the properties
        /// of the described type, filtered by the specified attributes.</returns>
        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection colUpdatedDescriptors = null;  // holds the property descriptors for returning
            IPropertyVisibilityDecorator visibilityDecorator = null;    // holds the property visibility decorator instance
            IDictionary<Guid, DisplayMode> visibilityMap = null;        // holds the propery visibility map returned by the decorator
            PropertyDescriptor oDescriptor = null;                      // holds the instance of the property descriptor

            // return the default descriptors if the model standardValueProvider
            // does not implement the decorator interface
            if (!(this.ModelElement is IPropertyVisibilityDecorator)
                && !(this.ModelElement is IRolePlayerVisibilityDecorator
                || this.ModelElement is IRolePlayerProviderDecorator))
            {
                return this.GetDisplayProperties(this.ModelElement, ref oDescriptor);
            }

            // instantiate the updated descriptor collection
            colUpdatedDescriptors = new PropertyDescriptorCollection(null);

            // create the default descriptors and property descriptor cache
            if (this.DefaultDescriptors == null
                || this.DefaultDescriptors.Count < 0)
            {
                // get the default descriptors
                oDescriptor = null;
                this.DefaultDescriptors = this.GetDisplayProperties(this.ModelElement, ref oDescriptor);
                oDescriptor = null;
            }

            // check whether the model standardValueProvider implements the property visibility decorator
            if (this.ModelElement is IPropertyVisibilityDecorator)
            {
                // typecast the model standardValueProvider to the property visibility decorator
                visibilityDecorator = this.ModelElement as IPropertyVisibilityDecorator;

                // get the property visibility modes from the decorator
                visibilityMap = visibilityDecorator.GetPropertyVisibilityModes(this.DomainPropertyIDCollection);
            }
            else
            {
                // instantiates the dictionary
                visibilityMap = new Dictionary<Guid, DisplayMode>();

                // create the default descriptor collection
                foreach (Guid id in this.DomainPropertyIDCollection)
                {
                    if (!visibilityMap.ContainsKey(id))
                        visibilityMap.Add(id, DisplayMode.Default);
                }

            }
            // Create the domain property collection
            this.GetUpdatedDomainPropertyDescriptors(ref colUpdatedDescriptors, ref visibilityMap);

            // reset visibility map
            visibilityMap = null;

            // check whether the given model standardValueProvider is a role player visibility
            if (this.ModelElement is IRolePlayerVisibilityDecorator)
            {
                // get the visibility map from the model standardValueProvider
                visibilityMap = (this.ModelElement as
                    IRolePlayerVisibilityDecorator).GetRolePlayerVisibilityModes(this.DomainRolePlayerIDCollection);
            }
            else
            {
                // instantiates the dictionary
                visibilityMap = new Dictionary<Guid, DisplayMode>();

                // create the default descriptor collection
                foreach (Guid id in this.DomainRolePlayerIDCollection)
                {
                    if (!visibilityMap.ContainsKey(id))
                        visibilityMap.Add(id, DisplayMode.Default);
                }
            }
            // get the domain role descriptors
            this.GetUpdatedDomainRoleDescriptors(ref colUpdatedDescriptors, ref visibilityMap);


            // clean up
            visibilityDecorator = null;
            visibilityMap = null;
            oDescriptor = null;

            return colUpdatedDescriptors;
        }

        /// <summary>
        /// Creates the domain role player property descriptors
        /// </summary>
        /// <param name="colUpdatedDescriptors">reference of the updated descriptor collection</param>
        /// <param name="visibilityMap">reference of the instance of the visibility map</param>
        /// <returns>the number of the role player property descriptor created as integer</returns>
        private int GetUpdatedDomainRoleDescriptors(ref PropertyDescriptorCollection colUpdatedDescriptors
            , ref IDictionary<Guid, DisplayMode> visibilityMap)
        {
            PropertyDescriptor oDescriptor = null;      // holds the property descriptor
            bool bIncludeParent = false;                // need to include the parent ??
            int nIntialCount = 0;                       // holds the initial count

            // need to include parent??
            bIncludeParent = this.IncludeEmbeddingRelationshipProperties(this.ModelElement);
            // set the intial count
            nIntialCount = colUpdatedDescriptors.Count;

            // iterate through the role players
            foreach (DomainRoleInfo sourceRole in this.DomainRolePlayers)
            {
                // check the visibility map contains the domain role display mode
                if (visibilityMap.ContainsKey(sourceRole.Id))
                    oDescriptor = this.CreateRolePlayerPropertyDescriptor(sourceRole, visibilityMap[sourceRole.Id]);
                else
                    // check for the default descriptor
                    oDescriptor = this.DefaultDescriptors.Find(sourceRole.OppositeDomainRole.Name, true);

                // add the descriptor if not null
                if (oDescriptor != null)
                {
                    colUpdatedDescriptors.Add(oDescriptor);
                }

                // include the parent??
                if (bIncludeParent
                    && sourceRole.DomainRelationship.IsEmbedding
                    && !sourceRole.DomainRelationship.AllowsDuplicates
                    && !sourceRole.IsEmbedding)
                {
                    // get the default standardValueProvider link
                    ElementLink oLink = sourceRole.GetElementLinks<ElementLink>(this.ModelElement, true).FirstOrDefault();
                    // check for null
                    if (oLink != null)
                    {
                        // iterate through the descriptors
                        foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(oLink))
                        {
                            // add to the collection if not null
                            if (descriptor != null)
                                colUpdatedDescriptors.Add(descriptor);
                        }
                    }
                }
            }

            // return the updated count
            return colUpdatedDescriptors.Count - nIntialCount;
        }

        /// <summary>
        /// Creates the domain property descriptors based on the visibility map provided
        /// </summary>
        /// <param name="colUpdatedDescriptors">reference of the updated descriptor collection</param>
        /// <param name="visibilityMap">reference of the instance of the visibility map</param>
        /// <returns>the number of the role player property descriptor created as integer</returns>
        private int GetUpdatedDomainPropertyDescriptors(ref PropertyDescriptorCollection colUpdatedDescriptors
            , ref IDictionary<Guid, DisplayMode> visibilityMap)
        {
            PropertyDescriptor oDescriptor = null;                      // holds the property desscriptor

            // iterate through the properties
            foreach (DomainPropertyInfo oPropertyInfo in this.DomainProperties)
            {
                // get the default descriptor if display mode is default
                if (visibilityMap[oPropertyInfo.Id].Equals(DisplayMode.Default))
                    oDescriptor = this.DefaultDescriptors.Find(oPropertyInfo.Name, true);
                // sometimes default descriptor can be null
                // anyways, we are creating new descriptor if it is not a default descriptor
                // so both ways, no problem ;)
                else
                    oDescriptor = this.CreatePropertyDescriptor(oPropertyInfo, visibilityMap[oPropertyInfo.Id]);

                // add the new descriptor to the collection
                // note that a default descriptor can be null
                if (oDescriptor != null)
                    colUpdatedDescriptors.Add(oDescriptor);

                // reset the descriptor
                oDescriptor = null;
            }
            // return count
            return colUpdatedDescriptors.Count;
        }



        /// <summary>
        /// Create the instance of the new property descriptor for the given display mode.
        /// </summary>
        /// <param name="oPropertyInfo">instance of the property info containing the property details.</param>
        /// <param name="displayMode">the display mode for the descriptor.</param>
        /// <returns>The instance of the ElementPropertyDescriptor.</returns>
        private ElementPropertyDescriptor CreatePropertyDescriptor(DomainPropertyInfo oPropertyInfo, DisplayMode displayMode)
        {
            if (displayMode != DisplayMode.Hidden)
                // create descriptor
                return this.CreatePropertyDescriptor(this.ModelElement
                    , oPropertyInfo
                    , this.GetCustomAttributes(oPropertyInfo.PropertyInfo, displayMode).ToArray());
            else
                return null;

        }

        /// <summary>
        /// Create new roleplayer property descriptor
        /// </summary>
        /// <param name="sourceRoleInfo">current domain role info</param>
        /// <param name="displayMode">the display mode</param>
        /// <returns>the collection of roleplayer property descriptor collection</returns>
        private RolePlayerPropertyDescriptor CreateRolePlayerPropertyDescriptor(DomainRoleInfo sourceRoleInfo, DisplayMode displayMode)
        {
            // no descriptor for hidden properties
            if (displayMode != DisplayMode.Hidden)
                return new RolePlayerDescriptor(this.ModelElement
                    , sourceRoleInfo.OppositeDomainRole
                    , this.GetCustomAttributes(sourceRoleInfo.LinkPropertyInfo, displayMode).ToArray());
            else
                return null;
        }
        /// <summary>
        /// Gets the custom attributes of the given property for the given display mode
        /// </summary>
        /// <param name="oPropertyInfo">The instance of the property info</param>
        /// <param name="displayMode">the display mode</param>
        /// <returns></returns>
        private List<Attribute> GetCustomAttributes(PropertyInfo oPropertyInfo, DisplayMode displayMode)
        {
            // holds the attributes
            object[] arrAttributes = null;

            // temporarily stores the attributes
            List<Attribute> colAttributes = null;

            // get the attributes
            arrAttributes = oPropertyInfo.GetCustomAttributes(typeof(Attribute), true);

            // create attribute collection
            colAttributes = new List<Attribute>();

            // if display mode is not default
            // add all the attributes other than ReadOnly and Browsable attributes.
            // else add all attributes
            if (arrAttributes != null && arrAttributes.Length > 0)
            {
                foreach (Attribute oAttribute in arrAttributes)
                {
                    if (displayMode == DisplayMode.Default
                        || !(oAttribute is ReadOnlyAttribute || oAttribute is BrowsableAttribute)
                        && !(displayMode == DisplayMode.ReadOnly && oAttribute is EditorAttribute))
                    {
                        colAttributes.Add(oAttribute);
                    }
                }
            }

            // set edit mode attributes
            if (displayMode == DisplayMode.ReadOnly)
                colAttributes.Add(new ReadOnlyAttribute(true));
            else if (displayMode == DisplayMode.Hidden)
                colAttributes.Add(new BrowsableAttribute(false));

            arrAttributes = null;

            return colAttributes;
        }

        /// <summary>
        /// Gets the type of the model standardValueProvider instance wrapped by the property descriptor.
        /// </summary>
        protected override Type ComponentType
        {
            get
            {
                return this.ModelElement != null ? this.ModelElement.GetType() : null;
            }
        }
    }
}
